#include "v4l2api.h"

#include <QDebug>

V4l2Api::V4l2Api(const char*deviceName)
{
    //[1] 打开设备 ---- /dev/video7
    fd = open(deviceName,O_RDWR);
    if(fd <0)
    {
        perror("打开设备失败");
        return ;
    }
    qDebug()<<"V4l2Api fd:"<<fd;

    //3、设置 摄像头 采集 数据的格式 --VIDIOC_S_FMT
    //比如说 设置 摄像头 采集 图像 的宽度 和 高度
    struct v4l2_format vfmt;
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vfmt.fmt.pix.width = 640;//宽度
    vfmt.fmt.pix.height = 480; //高度
    vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//采集的数据格式为 yuyv格式

    int ret = ioctl(fd,VIDIOC_S_FMT,&vfmt);
    if(ret<0)
    {
        perror("设置格式 失败");
        return ;
    }

    //获取VIDIOC_G_FMT 这个设置好的摄像头格式的信息 ，再判断 是否设置成功
    memset(&vfmt,0,sizeof(vfmt));
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    ret = ioctl(fd,VIDIOC_G_FMT,&vfmt);
    if(ret<0)
    {
        perror("获取格式 失败");
        return ;
    }
    if(vfmt.fmt.pix.width == 640 &&  vfmt.fmt.pix.height == 480 && vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV)
    {
        printf("设置成功\n");
    }else{
        printf("设置失败");
    }

    //4、申请内核空间 ---- 向内核申请一个缓冲区队列（该队列中有4个元素）
    struct v4l2_requestbuffers reqbuffers;
    reqbuffers.count = 4; //申请4个缓冲区
    reqbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;// 摄像头采集
    reqbuffers.memory = V4L2_MEMORY_MMAP; // mmap
    //向内核空间打一个报告，向你申请4个缓冲区队列，申请的方式为内存映射
    ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuffers); //获取的    信息存 放在该结构体
    if(ret < 0)
    {
        perror("申请队列空间失败");
        return ;
    }
    //5、将申请好的这个缓冲区队列（4个缓存内存） 映射到用户空间

    struct v4l2_buffer mapbuffer;
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    for(int i=0;i<4;i++){
        mapbuffer.index = i; //申请的缓冲区编号
        ret = ioctl(fd,VIDIOC_QUERYBUF,&mapbuffer);
        if(ret < 0) {
            perror("查询内核空间队列失败\n");
            return ;
        }
        //真正来实现内存映射 ,该函数的返回值是一个内存的首地址
        mptr[i] = (unsigned char*)mmap(NULL,mapbuffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);
        size[i] = mapbuffer.length;

        //通知使用完毕，----放回内核
        //#define VIDIOC_QBUF        _IOWR('V', 15, struct v4l2_buffer)
        ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer);
        if(ret < 0){
            perror("放回失败\n");
            return ;
        }
    }
}
//开启摄像头
void V4l2Api::start()
{
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int ret = ioctl(fd,VIDIOC_STREAMON,&type);
    if(ret < 0)
    {
        perror("开启失败\n");
        return ;
    }
}

void V4l2Api::stop()
{
    //#define VIDIOC_STREAMOFF	 _IOW('V', 19, int)
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int ret = ioctl(fd,VIDIOC_STREAMOFF,&type);
    if(ret < 0)
    {
        perror("停止失败\n");
        return ;
    }
}

void V4l2Api::destory()
{
    //解除内存映射
    //释放内存 int munmap(void *addr, size_t length);
    for(int i=0;i<4;i++)
    {
        munmap(mptr[i],size[i]);
    }
    //[9] 关闭设备
    ::close(fd);

}

void V4l2Api::get_frame(char *buffer, int *size)
{
    //采集数据 --VIDIOC_DQBUF：把数据从缓存中读取出来
    //#define VIDIOC_DQBUF        _IOWR('V', 17, struct v4l2_buffer)
    //1) 先查询当前的摄像头数据 在哪个 缓冲区中
    struct v4l2_buffer readbuffer;
    readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int ret = ioctl(fd,VIDIOC_DQBUF,&readbuffer);
    if(ret < 0)
    {
        perror("提取数据失败");
        return  ;
    }
    //使用内存拷贝 -- void *memcpy(void *dest, const void *src, size_t n);
    memcpy(buffer,mptr[readbuffer.index],readbuffer.length);
    *size = readbuffer.length;
    //VIDIOC_QBUF：把数据放回缓存队列
    //通知内核已使用完毕
    //#define VIDIOC_QBUF        _IOWR('V', 15, struct v4l2_buffer)
     ret = ioctl(fd,VIDIOC_QBUF,&readbuffer);
    if(ret < 0)
    {
        perror("放回队列失败");
        return  ;
    }
}


bool V4l2Api::yuv422torgb24(unsigned char *buffer, unsigned char *rgbdata, int w, int h)
{
    int r1, g1, b1;
    int r2, g2, b2;

    for(int i=0; i<w*h/2; i++)
    {
        char data[4];
        memcpy(data, buffer+i*4, 4);
        //Y0U0Y1V1  -->[Y0 U0 V1] [Y1 U0 V1]
        unsigned char Y0=data[0];
        unsigned char U0=data[1];
        unsigned char Y1=data[2];
        unsigned char V1=data[3];
        //yuyv --->rgb  rgb
        r1 = Y0+1.4075*(V1-128); if(r1>255)r1=255; if(r1<0)r1=0;
        g1 =Y0- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g1>255)g1=255; if(g1<0)g1=0;
        b1 = Y0 + 1.779 * (U0-128);  if(b1>255)b1=255; if(b1<0)b1=0;
        r2 = Y1+1.4075*(V1-128);if(r2>255)r2=255; if(r2<0)r2=0;
        g2 = Y1- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g2>255)g2=255; if(g2<0)g2=0;
        b2 = Y1 + 1.779 * (U0-128);  if(b2>255)b2=255; if(b2<0)b2=0;

        rgbdata[i*6+0]=r1;
        rgbdata[i*6+1]=g1;
        rgbdata[i*6+2]=b1;
        rgbdata[i*6+3]=r2;
        rgbdata[i*6+4]=g2;
        rgbdata[i*6+5]=b2;
    }
}
